package ui;

import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.Timer;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.UIManager;

import util.ResourceManager;
import algorithm.AbstractDistanceFinder;
import algorithm.ImprovedNlognDistanceFinder;
import algorithm.N2DistanceFinder;
import algorithm.NlognDistanceFinder;

public class DistanceCalculatorFrame extends JFrame {
	private static final int WINDOW_WIDTH = 500;
	private static final int WINDOW_HEIGHT = 700;

	private JTextArea pointArea;
	private JTextArea logArea;
	private JButton inputPointButton;
	private JButton randomPointButton;
	private JButton viewPointButton;
	private JButton startButton;
	private JButton viewVisualReaultButton;
	private JComboBox algorithmComboBox;

	private ResourceManager manager = ResourceManager.getResourceManager();

	private AbstractDistanceFinder[] finders = new AbstractDistanceFinder[3];

	public DistanceCalculatorFrame() {
		super("Distance Calculator");
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			e.printStackTrace();
		}
		int startX = (Toolkit.getDefaultToolkit().getScreenSize().width - WINDOW_WIDTH) / 2;
		int startY = (Toolkit.getDefaultToolkit().getScreenSize().height - WINDOW_HEIGHT) / 2;
		this.setBounds(startX, startY, WINDOW_WIDTH, WINDOW_HEIGHT);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		initComponents();
		finders[0] = new N2DistanceFinder();
		finders[1] = new NlognDistanceFinder();
		finders[2] = new ImprovedNlognDistanceFinder();
		refresh();
	}

	private void initComponents() {
		BorderLayout borderLayout = new BorderLayout();
		borderLayout.setHgap(5);
		borderLayout.setVgap(5);
		this.setLayout(borderLayout);
		this.add("East", new JPanel());
		this.add("West", new JPanel());
		this.add("North", new JPanel());
		this.add("South", new JPanel());
		JPanel panel = new JPanel();
		this.add("Center", panel);
		BoxLayout bl = new BoxLayout(panel, BoxLayout.Y_AXIS);
		panel.setLayout(bl);

		JPanel pointPane = new JPanel(new BorderLayout());
		JLabel pointLabel = new JLabel("Points:");
		pointPane.add("North", pointLabel);
		pointArea = new JTextArea();
		pointArea.setEditable(false);
		pointArea.setLineWrap(true);
		pointArea.setBorder(BorderFactory.createEtchedBorder());
		pointPane.add("Center", pointArea);
		JPanel buttonPanel1 = new JPanel();
		inputPointButton = new JButton("Input Points");
		inputPointButton.addActionListener(new InputPointAction());
		randomPointButton = new JButton("Random Points");
		randomPointButton.addActionListener(new RandomPointAction());
		viewPointButton = new JButton("View Points");
		viewPointButton.addActionListener(new ViewPointAction());
		buttonPanel1.add(inputPointButton);
		buttonPanel1.add(randomPointButton);
		buttonPanel1.add(viewPointButton);

		pointPane.add("South", buttonPanel1);
		panel.add(pointPane);

		panel.add(Box.createVerticalStrut(10));

		JPanel startPane = new JPanel();
		startPane.setLayout(new BoxLayout(startPane, BoxLayout.X_AXIS));
		algorithmComboBox = new JComboBox(new String[] { "n*n algorithm",
				"n*logn algorithm", "Improved n*logn algorithm" });
		startButton = new JButton("  Start  ");
		startButton.addActionListener(new StartAction());
		startPane.add(new JLabel("Select Algorithm:"));
		startPane.add(algorithmComboBox);
		startPane.add(Box.createHorizontalGlue());
		startPane.add(startButton);
		panel.add(startPane);

		panel.add(Box.createVerticalStrut(10));

		JPanel resultPane = new JPanel(new BorderLayout());
		JLabel resultLabel = new JLabel("Result:");
		resultPane.add("North", resultLabel);
		logArea = new JTextArea();
		logArea.setEditable(false);
		logArea.setLineWrap(true);
		logArea.setBorder(BorderFactory.createEtchedBorder());
		resultPane.add("Center", logArea);

		JPanel buttonPanel2 = new JPanel();
		viewVisualReaultButton = new JButton("View In Canvas");
		viewVisualReaultButton.addActionListener(new ViewResultAction());
		buttonPanel2.add(viewVisualReaultButton);
		resultPane.add("South", buttonPanel2);

		panel.add(resultPane);
	}

	private void refresh() {
		if (manager.getPoints() == null || manager.getPoints().length == 0) {
			pointArea
					.setText("No point selected, press \"Input Point\" button to input some points.");
			startButton.setEnabled(false);
		} else {
			Point[] pts = manager.getPoints();
			pointArea.setText("");
			for (int i = 0; i < pts.length; i++) {
				pointArea.append("[" + pts[i].x + "," + pts[i].y + "]\t");
				if ((i + 1) % 3 == 0) {
					pointArea.append("\n");
				}
				if (i == 17) {
					pointArea.append("...");

					break;
				}
			}
			pointArea.append("\n" + pts.length + " points in total.");
			startButton.setEnabled(true);
		}

		if (manager.isResultCalculated()) {
			logArea.setText("");
			logArea.append("Nearest Point Pair: " + "["
					+ manager.getNearestPointPair()[0].x + ","
					+ manager.getNearestPointPair()[0].y + "] ["
					+ manager.getNearestPointPair()[1].x + ","
					+ manager.getNearestPointPair()[1].y + "]\n");
			logArea.append("Distance: " + manager.getNearestDistance() + "\n");
			logArea.append("Consumed Time: " + manager.getTimeConsumed()
					+ " ns(nano second).");
		}
	}

	class StartAction extends AbstractAction {
		public void actionPerformed(ActionEvent arg0) {
			Timer timer = new Timer();
			long start = System.nanoTime();
			Point[] pts = finders[algorithmComboBox.getSelectedIndex()]
					.findMinimumDistance(manager.getPoints());
			long end = System.nanoTime();
			manager.setResultCalculated(true);
			manager.setNearestPointPair(pts);
			manager.setTimeConsumed(end - start);
			refresh();
		}
	}

	class InputPointAction extends AbstractAction {
		public void actionPerformed(ActionEvent arg0) {
			CanvasDialog dialog = new CanvasDialog(
					DistanceCalculatorFrame.this, CanvasDialog.INPUT_MODE);
			dialog.setVisible(true);
			if (dialog.getReturnStatus() == CanvasDialog.OK) {
				manager.setPoints(dialog.getPoints().toArray(new Point[0]));
				refresh();
			}
		}
	}

	class RandomPointAction extends AbstractAction {
		public void actionPerformed(ActionEvent arg0) {
			RandomPointDialog dialog = new RandomPointDialog(
					DistanceCalculatorFrame.this);
			dialog.setVisible(true);
			if (dialog.getReturnStatus() == CanvasDialog.OK) {
				int quantity = dialog.getQuantity();
				int canvasLength = ResourceManager.calcCanvasRange(quantity);
				Point[] pts = new Point[quantity];
				Set<Point> set = new HashSet<Point>();
				Random rdm = new Random();
				for (int i = 0; i < quantity; i++) {
					Point pt = new Point();
					pt.x = rdm.nextInt(canvasLength);
					pt.y = rdm.nextInt(canvasLength);
					while (set.contains(pt)) {
						pt.x = rdm.nextInt(canvasLength);
						pt.y = rdm.nextInt(canvasLength);
					}
					set.add(pt);
					pts[i] = pt;
				}
				set.clear();
				manager.setPoints(pts);
				refresh();
			}
		}
	}

	class ViewPointAction extends AbstractAction {
		public void actionPerformed(ActionEvent arg0) {
			CanvasDialog dialog = new CanvasDialog(
					DistanceCalculatorFrame.this, CanvasDialog.DISPLAY_MODE);
			List<Point> pts = new LinkedList<Point>();
			for (Point pt : manager.getPoints()) {
				pts.add(pt);
			}
			dialog.setPoints(pts);
			dialog.setVisible(true);
			if (dialog.getReturnStatus() == CanvasDialog.OK) {
				manager.setPoints(dialog.getPoints().toArray(new Point[0]));
				refresh();
			}
		}
	}

	class ViewResultAction extends AbstractAction {
		public void actionPerformed(ActionEvent arg0) {
			if (manager.isResultCalculated()) {
				CanvasDialog dialog = new CanvasDialog(
						DistanceCalculatorFrame.this, CanvasDialog.DISPLAY_MODE);
				List<Point> pts = new LinkedList<Point>();
				for (Point pt : manager.getPoints()) {
					pts.add(pt);
				}
				dialog.setPoints(pts);
				dialog.setNearestPointPair(manager.getNearestPointPair());
				dialog.setVisible(true);
				if (dialog.getReturnStatus() == CanvasDialog.OK) {
					manager.setPoints(dialog.getPoints().toArray(new Point[0]));
					refresh();
				}
			}
		}
	}
}
